#include "HIRCode.h"
#include "HXD1.h"
#include <cassert>

unsigned int HIRCode::ymr = 0U;

HIRCode::HIRCode()
{
	// We only allow one IR table
	assert(++ymr == 1U);
	_current_function = SIZE_MAX;
	_nbuiltin = 0ul;
}

HIRCode::~HIRCode()
{
	assert(--ymr == 0U);
}

/*
 * Add builtin functions from functable `ftable`.
 * Builtin functions must have indexes from 0 to `nbuiltin - 1`
 * both in function table and in IR code table!
 */
void HIRCode::add_builtin(const HFunctionTable &ftable, size_t nbuiltin)
{
	_nbuiltin = nbuiltin;
	HIRFunct builtin_func;
	builtin_func.nalloca = 0u;
	for (size_t i = 0ul; i < nbuiltin; ++i)
	{
		builtin_func.nparams = ftable.func(i).paramtype.size();
		builtin_func.id = ftable.func(i).funcid;
		builtin_func.rettype = CACTBtype2HIRBtype(ftable.func(i).rettype);
		for (size_t ip = 0ul; ip < builtin_func.nparams; ++ip)
		{
			auto ptype = ftable.func(i).paramtype.at(ip);
			HIRTemp ta(ptype->dim.size() > 0u
				? HIRBtype::POINTER : CACTBtype2HIRBtype(ptype->base_type));
		}
		_funct.insert(_funct.begin() + i, builtin_func);
	}
}

/*
 * Enter a function to generate IR code.
 * Make sure old function has been exited.
 * Return `funcidx` or throw exception if it is not in `_funct`.
 */
size_t HIRCode::enter_function(size_t funcidx)
{
	if (_current_function != SIZE_MAX)
		throw std::logic_error(
			"HIRCode: entering a function before the old is exited"
		);
	if (funcidx >= _funct.size())
		throw std::logic_error(
			std::string("HIRCode: cannot enter a function: ")
			+ std::to_string(funcidx)
		);
	else
		_current_function = funcidx;
	return funcidx;
}

/*
 * Add an external variable and return the index or
 * `SIZE_MAX` if already exists.
 * `hsteidx` is the idx in the global (root) symbol table.
 */
size_t HIRCode::add_extvar(const HSTEntry &hstentry, const void *data)
{
	extvar_t temp;
	size_t s;
	uint32_t bsize;

	if (search_extvar(hstentry.id) != SIZE_MAX)
		return SIZE_MAX;
	temp.id = hstentry.id;
	temp.data = data;
	temp.is_array = (hstentry.type->dim.size() > 0ul);
	temp.is_const = hstentry.type->is_const;
	temp.btype = CACTBtype2HIRBtype(hstentry.type->base_type);
	if (temp.btype == HIRBtype::UNKNOWN)
		throw std::logic_error(temp.id + " has invalid type");
	temp.length = 1ul;
	for (auto l : hstentry.type->dim)
		temp.length *= l;
	s = _extvar.size();
	bsize = cactsize(hstentry.type->base_type);
	if (hstentry.type->dim.size() == 0ul)
		temp.alignment = bsize > 4ul ? bsize : 4ul;
	else
		temp.alignment = 16ul;
	_extvar.push_back(temp);
	return s;
}

/*
 * Add a function and return the pointer to it or
 * `SIZE_MAX` if already exists.
 * A pointer to the symbol table is needed to bind the
 * parameters with temporaries.
 */
size_t HIRCode::add_funct(const HFTEntry & hftentry,
		HSymbolTable *psymtb, const std::vector<size_t>& params)
{
	HIRFunct temp;
	size_t funct_size, pidx = 0ul;
	// hold it for a while
	auto old_function = _current_function;

	if (search_funct(hftentry.funcid) != SIZE_MAX)
		return SIZE_MAX;
	temp.id = hftentry.funcid;
	temp.rettype = CACTBtype2HIRBtype(hftentry.rettype);
	temp.nparams = hftentry.paramtype.size();
	if (temp.rettype == HIRBtype::UNKNOWN)
		throw std::logic_error(temp.id + " has invalid return type");
	// `temp.code` is empty at the beginning
	funct_size = _funct.size();
	_funct.push_back(temp);

	// set it for `new_temp()`...
	_current_function = funct_size;
	for (auto p : hftentry.paramtype)
	{
		HIRTempno ta;
		if (p->dim.size() > 0u)
		{
			// consider an array parameter as pointer
			ta = new_temp(HIRBtype::POINTER);
			psymtb->symbol(params.at(pidx)).latn = INVALID_TEMP_NO;
			psymtb->symbol(params.at(pidx)).pttn = ta;
		}
		else
		{
			/*
			 * The cast is safe because `HIRBtype` and `Btype` are the same
			 * for base types. See `HIRBtype` for more information.
			 */
			ta = new_temp(CACTBtype2HIRBtype(p->base_type));
			psymtb->symbol(params.at(pidx)).latn = ta;
			psymtb->symbol(params.at(pidx)).pttn = INVALID_TEMP_NO;
		}
		/*
		 * Use 0 to initialze `fassigned` for function arguments,
		 * although it should be "-1".
		 */
		update_temp_fassigned(ta, 0u);
		if (pidx != ta)
			throw std::logic_error(std::string("The ") + std::to_string(pidx)
				+ "-th argument is bound with temp " + std::to_string(ta));
		++pidx;
	}
	
	_current_function = old_function;
	return funct_size;
}

/*
 * Search an external variable in IR table acccording its name,
 * return its index or `SIZE_MAX` if not found.
 */
size_t HIRCode::search_extvar(const std::string &id) const
{
	for (size_t i = 0u; i < _extvar.size(); ++i)
		if (_extvar.at(i).id == id)
			return i;
	return SIZE_MAX;
}


/*
 * Search a function in IR table acccording its name,
 * return its index or `SIZE_MAX` if not found.
 * NOTE: `code` of `_funct` may be modified, so we avoid using `const`
 * for the return value.
 */
size_t HIRCode::search_funct(const std::string &id) const
{
	for (size_t i = 0u; i < _funct.size(); ++i)
		if (_funct.at(i).id == id)
			return i;
	return SIZE_MAX;
}

// Create a new temporary and return its index.
HIRTempno HIRCode::new_temp(HIRBtype type)
{
	size_t s = _funct.at(_current_function).temps.size();
	HIRTemp ntemp(type);

	if (int(type) <= int(HIRBtype::UNKNOWN) ||
		int(type) >= int(HIRBtype::MAX))
		throw std::logic_error(
			"HIRCode: creating a temporary with unknown type"
		);

	_funct.at(_current_function).temps.push_back(ntemp);
	return s;
}

/*
 * Once a temp (or label) is assigned, please use this function
 * to update its `fassigned`.
 * `aiidx` is the index of the instruction that assigns it.
 */
void HIRCode::update_temp_fassigned(HIRTempno tempno, uint32_t aiidx)
{
	if (tempno > _funct.at(_current_function).temps.size())
		throw std::logic_error(
			"HIRCode: setting fassigned of a temporary out of range"
		);
	uint32_t oldfa = _funct.at(_current_function).temps.at(tempno).fassigned;
	if (aiidx < oldfa || oldfa == INVALID_INST_IDX)
		_funct.at(_current_function).temps.at(tempno).fassigned = aiidx;
}

/*
 * Once a temp (or label) is referenced (as right value),
 * please use this function to update its `frefed` and `lrefed`.
 * `riidx` is the index of the instruction that references it.
 */
void HIRCode::update_temp_flrefed(HIRTempno tempno, uint32_t riidx)
{
	if (tempno > _funct.at(_current_function).temps.size())
		throw std::logic_error(
			"HIRCode: setting f/lrefed of a temporary out of range"
		);
	uint32_t oldfr = _funct.at(_current_function).temps.at(tempno).frefed,
		oldlr = _funct.at(_current_function).temps.at(tempno).lrefed;
	if (oldfr == INVALID_INST_IDX || riidx < oldfr)
		_funct.at(_current_function).temps.at(tempno).frefed = riidx;
	if (oldlr == INVALID_INST_IDX || riidx > oldlr)
		_funct.at(_current_function).temps.at(tempno).lrefed = riidx;
}

/*
 * Append an instruction to the currrent function and
 * return its index.
 */
size_t HIRCode::append_inst(const HIRInst &inst)
{
	size_t s;
	if (_current_function == SIZE_MAX)
		throw std::logic_error(
			"HIRCode: appending instruction before entering function"
		);
	s = _funct.at(_current_function).ircode.size();
	_funct.at(_current_function).ircode.push_back(inst);
	return s;
}

/*
 * Insert an instruction at `loc` and return its index.
 * NOTE: please call this function first and then update
 * `fassigned`, `frefed` and `lrefed` as it will modify them!
 */
size_t HIRFunct::insert_inst(const HIRInst &inst, size_t loc)
{
	ircode.insert(ircode.begin() + loc, inst);
	/*
	 * Once an instruction is inserted, `fassigned` and `f/lrefed` of temporaries
	 * should be updated if the index is greater than or equal to `loc`.
	 */
	for (auto temp = temps.begin() + nparams; temp != temps.end(); ++temp)
	{	// parameters are skipped
		if (temp->fassigned >= loc && temp->fassigned != INVALID_INST_IDX)
			++temp->fassigned;
		if (temp->frefed >= loc && temp->frefed != INVALID_INST_IDX)
			++temp->frefed;
		if (temp->lrefed >= loc && temp->lrefed != INVALID_INST_IDX)
			++temp->lrefed;
	}
	return loc;
}

/*
 * Insert an instruction to the currrent function at `loc`
 * and return its index.
 * NOTE: please call this function first and then update
 * `fassigned`, `frefed` and `lrefed` as it will modify them!
 */
size_t HIRCode::insert_inst(const HIRInst &inst, size_t loc)
{
	if (_current_function == SIZE_MAX)
		throw std::logic_error(
			"HIRCode: inserting instruction before entering function"
		);
	return _funct.at(_current_function).insert_inst(inst, loc);
}

size_t HIRCode::append_ret(HIRBtype type, HIROpnd value)
{	// if `type` is `VOID`, `value` is meaningless
	HIRInst inst(HIROper::RET);
	size_t idx = _funct.at(_current_function).ircode.size();

	inst.type.base_type = type;
	inst.value = value;
	if (type != HIRBtype::VOID && value.tp == HIROpnd::TEMP)
		update_temp_flrefed(value.temp, idx);
	return append_inst(inst);
}

size_t HIRCode::append_br(HIRTempno cond, HIRLabel iftrue, HIRLabel iffalse)
{
	HIRInst inst(HIROper::BRc);
	size_t idx = _funct.at(_current_function).ircode.size();

	inst.cond = cond;
	inst.iftrue = iftrue;
	inst.iffalse = iffalse;
	update_temp_flrefed(cond, idx);
	update_temp_flrefed(iftrue, idx);
	update_temp_flrefed(iffalse, idx);
	return append_inst(inst);
}

size_t HIRCode::append_br(HIRLabel dest)
{
	HIRInst inst(HIROper::BRu);
	size_t idx = _funct.at(_current_function).ircode.size();

	inst.brdest = dest;
	update_temp_flrefed(dest, idx);
	return append_inst(inst);
}

size_t HIRCode::append_fneg(HIRTempno dest, HIRBtype type, HIROpnd op1)
{
	HIRInst inst(HIROper::FNEG, dest);
	size_t idx = _funct.at(_current_function).ircode.size();

	inst.type.base_type = type;
	inst.op1 = op1;
	update_temp_fassigned(dest, idx);
	if (op1.tp == HIROpnd::TEMP)
		update_temp_flrefed(op1.temp, idx);
	return append_inst(inst);
}

/*
 * binary arithmetic and bitwise instruction.
 * NOTE: a big patch has been added for `ADD` instruction!
 * If the `type` is floating point, replace it to `FADD` automatically.
 */
size_t HIRCode::append_arlo(HIROper op, HIRTempno dest, HIRBtype type,
	const HIROpnd &op1, const HIROpnd &op2, const char *cominfo,
	bool flag_garbage)
{
	// A big patch!
	if (op == HIROper::ADD &&
		(type == HIRBtype::FLOAT || type == HIRBtype::DOUBLE))
		op = HIROper::FADD;

	HIRInst inst(op, dest);
	size_t idx = _funct.at(_current_function).ircode.size();

	inst.type.base_type = type;
	inst.op1 = op1;
	inst.op2 = op2;
	inst.flag_garbage = flag_garbage;
	if (cominfo != nullptr)
		inst.cominfo = new std::string(cominfo);
	update_temp_fassigned(dest, idx);
	if (op1.tp == HIROpnd::TEMP)
		update_temp_flrefed(op1.temp, idx);
	if (op2.tp == HIROpnd::TEMP)
		update_temp_flrefed(op2.temp, idx);
	return append_inst(inst);
}

/*
 * We ensure that all `ALLOCA` instructions are at the beginning
 * of a function, so insertion is used rather than appending.
 */
size_t HIRCode::insert_alloca(HIRTempno dest, const HIRType &type,
	uint32_t alignment, const char *cominfo)
{
	HIRInst inst(HIROper::ALLOCA, dest);
	size_t nalloca = _funct.at(_current_function).nalloca++,
		irt;

	inst.type = type;
	inst.alignment = alignment;
	if (cominfo != nullptr)
		inst.cominfo = new std::string(cominfo);
	
	/*
	 * We must update `fassigned` after inserting the instruction,
	 * because `insert_inst()` would modify it!
	 */
	irt = insert_inst(inst, nalloca);
	update_temp_fassigned(dest, nalloca);

	return irt;
}

size_t HIRCode::append_load(HIRTempno dest, HIRBtype type, HIRTempno ptr)
{
	HIRInst inst(HIROper::LOAD, dest);
	size_t idx = _funct.at(_current_function).ircode.size();

	inst.type.base_type = type;
	inst.pointer.tp = HIROpnd::TEMP;
	inst.pointer.temp = ptr;
	update_temp_fassigned(dest, idx);
	update_temp_flrefed(ptr, idx);
	return append_inst(inst);
}

size_t HIRCode::append_load(HIRTempno dest, HIRBtype type, size_t xvidx)
{
	HIRInst inst(HIROper::LOAD, dest);
	size_t idx = _funct.at(_current_function).ircode.size();

	inst.type.base_type = type;
	inst.pointer.tp = HIROpnd::XVAR;
	inst.pointer.xvaridx = xvidx;
	update_temp_fassigned(dest, idx);
	return append_inst(inst);
}

size_t HIRCode::append_store(HIRBtype type, const HIROpnd &ptr, const HIROpnd &value,
	const char *cominfo)
{
	HIRInst inst(HIROper::STORE);
	size_t idx = _funct.at(_current_function).ircode.size();

	inst.type.base_type = type;
	inst.pointer = ptr;
	inst.value = value;
	if (cominfo != nullptr)
		inst.cominfo = new std::string(cominfo);
	if (ptr.tp == HIROpnd::TEMP)
		update_temp_flrefed(ptr.temp, idx);
	if (value.tp == HIROpnd::TEMP)
		update_temp_flrefed(value.temp, idx);
	return append_inst(inst);
}

size_t HIRCode::append_icmp(HIRTempno dest, HIRBtype type, ICMPCond cond,
	const HIROpnd &op1, const HIROpnd &op2)
{
	HIRInst inst(HIROper::ICMP, dest);
	size_t idx = _funct.at(_current_function).ircode.size();

	inst.type.base_type = type;
	inst.op1 = op1;
	inst.op2 = op2;
	inst.icond = cond;
	update_temp_fassigned(dest, idx);
	if (op1.tp == HIROpnd::TEMP)
		update_temp_flrefed(op1.temp, idx);
	if (op2.tp == HIROpnd::TEMP)
		update_temp_flrefed(op2.temp, idx);
	return append_inst(inst);
}

size_t HIRCode::append_fcmp(HIRTempno dest, HIRBtype type, FCMPCond cond,
	const HIROpnd &op1, const HIROpnd &op2)
{
	HIRInst inst(HIROper::FCMP, dest);
	size_t idx = _funct.at(_current_function).ircode.size();

	inst.type.base_type = type;
	inst.op1 = op1;
	inst.op2 = op2;
	inst.fcond = cond;
	update_temp_fassigned(dest, idx);
	if (op1.tp == HIROpnd::TEMP)
		update_temp_flrefed(op1.temp, idx);
	if (op2.tp == HIROpnd::TEMP)
		update_temp_flrefed(op2.temp, idx);
	return append_inst(inst);
}

size_t HIRCode::append_phi(HIRTempno dest, HIRBtype type,
	HIRLabel label0, const HIROpnd &val0, HIRLabel label1, const HIROpnd &val1)
{
	HIRInst inst(HIROper::PHI, dest);
	size_t idx = _funct.at(_current_function).ircode.size();

	inst.type.base_type = type;
	inst.label0 = label0;
	inst.val0 = val0;
	inst.label1 = label1;
	inst.val1 = val1;
	update_temp_fassigned(dest, idx);
	update_temp_flrefed(label0, idx);
	update_temp_flrefed(label1, idx);
	if (val0.tp == HIROpnd::TEMP)
		update_temp_flrefed(val0.temp, idx);
	if (val1.tp == HIROpnd::TEMP)
		update_temp_flrefed(val1.temp, idx);
	return append_inst(inst);
}

size_t HIRCode::append_call(HIRBtype type, size_t funcidx,
	std::vector<HIRFarg> *pfargs, HIRTempno dest)
{
	HIRInst inst(HIROper::CALL, dest);
	size_t idx = _funct.at(_current_function).ircode.size();

	inst.type.base_type = type;
	inst.funcidx = funcidx;
	inst.pfargs = pfargs;
	if (dest != INVALID_TEMP_NO)
		update_temp_fassigned(dest, idx);
	if (pfargs != nullptr)
		for (auto & farg : *pfargs)
			if (farg.arg.tp == HIROpnd::TEMP)
				update_temp_flrefed(farg.arg.temp, idx);
	return append_inst(inst);
}

size_t HIRCode::append_label(HIRLabel labelno)
{
	HIRInst inst(HIROper::LABEL);
	size_t idx = _funct.at(_current_function).ircode.size();

	inst.labelno = labelno;
	update_temp_fassigned(labelno, idx);
	return append_inst(inst);
}

// append empty lines to make it more readable
size_t HIRCode::append_emptylines(uint32_t nel)
{
	HIRInst empty(HIROper::EMPTY_LINE);

	if (nel > 10u)
		nel = 10u;
	while (nel-- > 0u)
		append_inst(empty);
	return _funct.at(_current_function).ircode.size() - 1ul;
}

void HIRCode::dump(std::ostream &os, const char *src_fname) const
{
	extern const HIRCode *dump_ircode;
	// command line arguments
	extern std::vector<std::string> cmd_args;
	dump_ircode = this;

	if (src_fname == nullptr)
		src_fname = "<Unknown>";

	os << "; Dumped by HXD1 from source file \"" << src_fname << "\" with -O";

	if (opts.debug_level != 0)
		os << Optcode[opts.opt_level];
	else
		os << opts.opt_level;

	// print command line arguments
	if (opts.debug_level != 0)
	{
		os << std::endl << ';';
		for (auto & arg : cmd_args)
			os << ' ' << arg;
		os << std::endl;
	}
	os << std::endl;
	for (auto & xvar : _extvar)
		os << xvar;
	os << std::endl;
	for (size_t i = 0ul; i < _funct.size(); ++i)
	{
		if (opts.dump_builtin != 0 || i >= _nbuiltin)
			os << _funct.at(i) << std::endl;
	}
	dump_ircode = nullptr;
}

/*
 * Get the destination TEMPorary of the HXD1 instruction.
 * Return the temp number or `INVALID_TEMP_NO` if the instruction
 * has no destination.
 * NOTE: label is not considered as destination!
 */
HIRTempno HIRInst::get_dest() const
{
	if (oper == HIROper::BRc || oper == HIROper::BRu
		|| oper == HIROper::RET || oper == HIROper::STORE
		|| oper == HIROper::EMPTY_LINE || oper == HIROper::LABEL)
		return INVALID_TEMP_NO;
	return dest;
}

/*
 * Get all source temporaries of the instruction `inst`,
 * put their numbers into `srctemps` and return the size of it.
 */
size_t HIRInst::get_srctemps(std::vector<HIRTempno> &srctemps) const
{
	srctemps.clear();
#define CHECK_AND_PUSHBACK(opnd)			\
	if (opnd.tp == HIROpnd::TEMP)			\
		srctemps.push_back(opnd.temp);	

	switch (oper)
	{
	case HIROper::RET:
		CHECK_AND_PUSHBACK(value);
		break;
	case HIROper::BRc:
		srctemps.push_back(cond);
		break;
	case HIROper::FNEG:
		CHECK_AND_PUSHBACK(op1);
		break;
	case HIROper::ADD: case HIROper::FADD: case HIROper::SUB: case HIROper::FSUB:
	case HIROper::MUL: case HIROper::FMUL: case HIROper::UDIV: case HIROper::SDIV:
	case HIROper::FDIV: case HIROper::UREM: case HIROper::SREM:
	case HIROper::SHL: case HIROper::LSHR: case HIROper::ASHR:
	case HIROper::AND: case HIROper::OR: case HIROper::XOR:
	case HIROper::ICMP: case HIROper::FCMP:
		CHECK_AND_PUSHBACK(op1);
		CHECK_AND_PUSHBACK(op2);
		break;
	case HIROper::STORE:
		CHECK_AND_PUSHBACK(value);
	case HIROper::LOAD:
		CHECK_AND_PUSHBACK(pointer);
		break;
	case HIROper::PHI:
		CHECK_AND_PUSHBACK(val0);
		CHECK_AND_PUSHBACK(val1);
		break;
	case HIROper::CALL:
		if (pfargs != nullptr)
			for (auto & arg : *pfargs)
				CHECK_AND_PUSHBACK(arg.arg);
		break;
	default:
		break;
	}
	for (auto temp : srctemps)
		if (temp == INVALID_TEMP_NO)
			throw std::logic_error(
				std::string("an instruction with invalid temp is found:")
				+ std::to_string(int(oper))
			);
	return srctemps.size();
#undef CHECK_AND_PUSHBACK
}

/*
 * Decide whether the two instructions have the same
 * operation and source operands.
 * If `ignore_side_effect` is NOT set, instructions such as
 * `LOAD`, `STORE`, `CALL` that may have side effects will NOT be compared
 * and just be considered as unequal!
 * This is used to detect common subexpressions.
 */
bool HIRInst::opequals(const HIRInst& inst, bool ignore_side_effect) const
{
	if (oper != inst.oper ||
		type.base_type != inst.type.base_type)
		return false;
	switch (oper)
	{
	case HIROper::RET:
		return ignore_side_effect && value == inst.value;
		break;
	case HIROper::FNEG:
		return op1 == inst.op1;
		break;
	case HIROper::ADD: case HIROper::FADD: case HIROper::SUB: case HIROper::FSUB:
	case HIROper::MUL: case HIROper::FMUL: case HIROper::UDIV: case HIROper::SDIV:
	case HIROper::FDIV: case HIROper::UREM: case HIROper::SREM:
	case HIROper::SHL: case HIROper::LSHR: case HIROper::ASHR:
	case HIROper::AND: case HIROper::OR: case HIROper::XOR:
	case HIROper::ICMP: case HIROper::FCMP:
		return op1 == inst.op1 && op2 == inst.op2;
		break;
	case HIROper::STORE:
		return ignore_side_effect && value == inst.value;
	case HIROper::LOAD:
		return ignore_side_effect && pointer == inst.pointer;
		break;
	case HIROper::PHI:
		return val0 == inst.val0 && val1 == inst.val1;
		break;
	case HIROper::CALL:
	{
		bool ret = ignore_side_effect;
		size_t aidx;
		if ((pfargs != nullptr && inst.pfargs != nullptr
			&& pfargs->size() == inst.pfargs->size())
			|| (pfargs == nullptr && inst.pfargs == nullptr))
		{
			if (pfargs != nullptr)
				for (aidx = 0ul; ret == true && aidx < pfargs->size(); ++aidx)
					if (pfargs->at(aidx).type != inst.pfargs->at(aidx).type
					|| !(pfargs->at(aidx).arg == inst.pfargs->at(aidx).arg))
						ret = false;
		}
		else
			ret = false;
		return ret;
	}
		break;
	default:
		return false;
		break;
	}
}

/*
 * Replace source temps of the inst that are `rip` with `rep`.
 * Return the number of temps replaced.
 */
size_t HIRInst::srctemps_replace(HIRTempno rip, HIRTempno rep)
{
	size_t rep_ymr = 0ul;
#define CHECK_AND_REPLACE(opnd)							\
	if (opnd.tp == HIROpnd::TEMP && opnd.temp == rip)	\
		opnd.temp = rep, ++rep_ymr;

	switch (oper)
	{
	case HIROper::RET:
		CHECK_AND_REPLACE(value);
		break;
	case HIROper::BRc:
		if (cond == rip)
			cond = rep, ++rep_ymr;
		break;
	case HIROper::FNEG:
		CHECK_AND_REPLACE(op1);
		break;
	case HIROper::ADD: case HIROper::FADD: case HIROper::SUB: case HIROper::FSUB:
	case HIROper::MUL: case HIROper::FMUL: case HIROper::UDIV: case HIROper::SDIV:
	case HIROper::FDIV: case HIROper::UREM: case HIROper::SREM:
	case HIROper::SHL: case HIROper::LSHR: case HIROper::ASHR:
	case HIROper::AND: case HIROper::OR: case HIROper::XOR:
	case HIROper::ICMP: case HIROper::FCMP:
		CHECK_AND_REPLACE(op1);
		CHECK_AND_REPLACE(op2);
		break;
	case HIROper::STORE:
		CHECK_AND_REPLACE(value);
	case HIROper::LOAD:
		CHECK_AND_REPLACE(pointer);
		break;
	case HIROper::PHI:
		CHECK_AND_REPLACE(val0);
		CHECK_AND_REPLACE(val1);
		break;
	case HIROper::CALL:
		if (pfargs != nullptr)
			for (auto & arg : *pfargs)
				CHECK_AND_REPLACE(arg.arg);
		break;
	default:
		break;
	}
	return rep_ymr;
}

/*
 * For a `BRc` and `BRu` instruction, replace its all
 * branch destinations `rip` with `rep`.
 */
void HIRInst::brdests_replace(HIRLabel rip, HIRLabel rep)
{
	if (oper == HIROper::BRc)
	{
		if (iftrue == rip)
			iftrue = rep;
		if (iffalse == rip)
			iffalse = rep;
	}
	else if (oper == HIROper::BRu)
	{
		if (brdest == rip)
			brdest = rep;
	}
	else
		throw std::logic_error("Not a branch inst: " + std::to_string(int(oper)));
}

/*
 * Whether it is a copy (move) instruction or not?
 * If true, return the source temp;
 * otherwise return `INVALID_TEMP_NO`.
 */
HIRTempno HIRInst::is_copy() const
{
	if (oper == HIROper::FADD || oper == HIROper::ADD)
		if (op2.tp == HIROpnd::IMM && op2.imm.ival == 0l
			&& op1.tp == HIROpnd::TEMP)
			return op1.temp;
	return INVALID_TEMP_NO;
}

/*
 * Get the future use of temp numbered `temp`, starting from instruction
 * at `start_iidx`, in BB numbered `bbidx`.
 * As a special case, we allow `start_iidx` be greater than BB's `.end` by 1.
 * Return:
 * low 4 B:
 * 		0: never referenced nor assigned (NOT live just before `start_iidx`-th inst);
 * 		1: referenced before assigned again (LIVE);
 * 		2: assigned before referenced again (NOT live);
 * high 4 B:
 * 		Index of the first instruction that assigned or referenced it.
 */
uint64_t HIRFunct::get_temp_future_use(HIRTempno temp, uint32_t start_iidx, uint32_t bbidx)
	const
{
	// for return value
	uint32_t iidx, status = 0u, end;
	// source temps of an inst
	std::vector<HIRTempno> srcs;

	if (bbidx >= basic_blocks.size() || start_iidx < basic_blocks.at(bbidx).begin
		|| start_iidx > (end = basic_blocks.at(bbidx).end) + 1u)
		throw std::logic_error(
			std::string("invalid arguments for get_temp_..: ") + std::to_string(start_iidx)
			+ ',' + std::to_string(bbidx)
		);
	for (iidx = start_iidx; iidx <= end; ++iidx)
	{
		size_t si;
		ircode.at(iidx).get_srctemps(srcs);
		for (si = 0ul; si < srcs.size(); ++si)
			if (srcs.at(si) == temp)
				break;
		if (si < srcs.size())
		{	// found in `srcs`
			status = 1u;
			break;
		}
		else if (ircode.at(iidx).get_dest() == temp)
		{	// is a dest
			status = 2u;
			break;
		}
	}
	if (iidx > end)
		// never referenced nor assigned
		iidx = 0u;
	return (uint64_t(iidx) << 32) | uint64_t(status);
}